/*
 * Parsing module for GTF in BAT.
 * Copyright (C) 2006 Bob Zimmermann <rpz@cse.wustl.edu>
 *
 *  This file is a part of BAT.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * BAT_GTF_Parser.c
 *
 * Parser for BAT-formatted files.
 *
 * $Id: BAT_GTF_Parser.c 1406 2006-09-20 20:27:36Z rpz $
 *
 */

#include "BAT_GTF_Parser.h"

BAT_Parse_Plugin BAT_GTF_Parse_Plugin = {
  BAT_GTF_Parser_Type,
  BAT_GTF_can_parse,
  BAT_GTF_Parser_new,
  BAT_GTF_Parser_delete,
  BAT_GTF_Parser_parse_file
};

int _gtfp_parse_comment(BAT_GTF_Parse_Context *);
int _gtfp_parse_main(BAT_GTF_Parse_Context *);
int _gtfp_parse_attributes(BAT_GTF_Parse_Context *);
int _gtfp_parse_end_comment(BAT_GTF_Parse_Context *);
int _gtfp_next_token(BAT_GTF_Parse_Context *);
int _gtfp_add_feature(BAT_GTF_Parse_Context *);
int _gtfp_error(BAT_GTF_Parse_Context *, char *msg);

void *
BAT_GTF_Parser_new()
{
  BAT_GTF_Parse_Context *pc;

  pc = malloc(sizeof(BAT_GTF_Parse_Context));
  pc->err = 0;
  pc->feats =
    darray_new(500, darray_ops_copy(&BAT_Owner_Ops[Free_Feature_Allocator]));
  pc->gid_attr = NULL;
  pc->tid_attr = NULL;
  pc->attr = NULL;
  pc->cmt = NULL;

  return (void *) pc;
}

void
BAT_GTF_Parser_delete(void *p, int befrei)
{
  BAT_GTF_Parse_Context *pc = p;
  if (pc->feats != NULL)
    darray_delete(pc->feats, 1);

  if (pc->feat != NULL)
    BAT_Free_Feature_delete(pc->feat, 1);

  if (pc->attr != NULL)
    BAT_Attribute_delete(pc->attr, 1);
  if (pc->gid_attr != NULL)
    BAT_Attribute_delete(pc->gid_attr, 1);
  if (pc->tid_attr != NULL)
    BAT_Attribute_delete(pc->tid_attr, 1);

  if (pc->cmt != NULL)
    BAT_Comment_delete(pc->cmt, 1);

  if (befrei)
    free(p);
}

int
BAT_GTF_can_parse(char *filename)
{
  char *ext = BAT_Parser_get_filename_extension(filename);
  if (!strcmp(ext, "gtf"))
    return 1;
  return 0;
}

BAT_Free_Feature **
BAT_GTF_Parser_parse_file(void *p, FILE * fh, da_pos_t *n_feats)
{
  BAT_GTF_Parse_Context *pc = p;
  BAT_Free_Feature **feats;

  if (pc->feats == NULL)
    pc->feats =
      darray_new(500, darray_ops_copy(&BAT_Owner_Ops[Free_Feature_Allocator]));

  pc->lineno = 0;
  pc->cmt = BAT_Comment_new();

  while (1) {
    pc->lineno++;
    pc->pos = 0;
    pc->start = 0;
    pc->err = 0;

    if (fgets(pc->line, 1023, fh) == NULL)
      break;
    if (_gtfp_parse_comment(pc))
      continue;

    pc->feat = BAT_Free_Feature_new();
    _gtfp_parse_main(pc);
    _gtfp_parse_attributes(pc);
    _gtfp_parse_end_comment(pc);
    if (!pc->err)
      BAT_Free_Feature_set_comment(pc->feat, pc->cmt);
    /* !!! NO MORE ERRORS CAN HAPPEN AFTER WE SET THE COMMENT !!! */

    if (!pc->err)
      darray_add(pc->feats, pc->feat);

    if (pc->lineno == 10000)
      pc->lineno = 10000;
    pc->cmt = BAT_Comment_new();
  }

  BAT_Comment_delete(pc->cmt, 1);
  pc->cmt = NULL;

  feats = (BAT_Free_Feature **) darray_release(pc->feats, n_feats, 1);
  pc->feats = NULL;
  pc->feat = NULL;

  return feats;
}

int
_gtfp_parse_comment(BAT_GTF_Parse_Context * pc)
{
  int i;

  if (pc->err)
    return 0;

  for (i = 0; i < BAT_GTF_LINE_SZ; i++) {
    /*
     * FIXME: are these all the newlines? What else is a blank
     * line?
     */
    if (pc->line[i] == '\n' || pc->line[i] == '\r' || pc->line[i] == '#') {
      BAT_Comment_add_line(pc->cmt, pc->line);
      return 1;
    }
    else if (isspace(pc->line[i]))
      continue;
    else
      return 0;
  }
  return 0;
}

int
_gtfp_parse_main(BAT_GTF_Parse_Context * pc)
{
  long frame, score;
  BAT_Coord coord;
  char c;

  if (pc->err)
    return 0;

  /* seqname */
  if (!_gtfp_next_token(pc))
    return _gtfp_error(pc, "No seqname found.");
  c = pc->line[pc->pos];
  pc->line[pc->pos] = '\0';
  BAT_Free_Feature_set_seqname(pc->feat, &pc->line[pc->start]);
  pc->line[pc->pos] = c;

  /* source */
  if (!_gtfp_next_token(pc))
    return _gtfp_error(pc, "No source found");
  c = pc->line[pc->pos];
  pc->line[pc->pos] = '\0';
  BAT_Free_Feature_set_source(pc->feat, &pc->line[pc->start]);
  pc->line[pc->pos] = c;

  /* feature_type */
  if (!_gtfp_next_token(pc))
    return _gtfp_error(pc, "No feature type found");
  c = pc->line[pc->pos];
  pc->line[pc->pos] = '\0';
  BAT_Free_Feature_set_feature_type(pc->feat,
				    BAT_Feature_Type_from_str(&pc->
							      line[pc->start]));
  pc->line[pc->pos] = c;

  /* start */
  if (!_gtfp_next_token(pc))
    return _gtfp_error(pc, "No start coord found");
  coord = strtol(&pc->line[pc->start], NULL, 0);
  if (coord < 1)
    return _gtfp_error(pc, "Invalid start coord");
  BAT_Free_Feature_set_start(pc->feat, coord);

  /* end */
  if (!_gtfp_next_token(pc))
    return _gtfp_error(pc, "No end coord found");
  coord = strtol(&pc->line[pc->start], NULL, 0);
  if (coord < 1)
    return _gtfp_error(pc, "Invalid end coord");
  BAT_Free_Feature_set_end(pc->feat, coord);

  /* score */
  if (!_gtfp_next_token(pc))
    return _gtfp_error(pc, "No score found");
  score = strtol(&pc->line[pc->start], NULL, 0);
  BAT_Free_Feature_set_score(pc->feat, score);

  /* strand */
  if (!_gtfp_next_token(pc))
    return _gtfp_error(pc, "No strand found");
  if (!isspace(pc->line[pc->start + 1]))
    BAT_Free_Feature_set_strand(pc->feat, STRAND);
  else {
    switch (pc->line[pc->start]) {
    case '+':
      BAT_Free_Feature_set_strand(pc->feat, PLUS);
      break;
    case '-':
      BAT_Free_Feature_set_strand(pc->feat, MINUS);
      break;
    default:
      BAT_Free_Feature_set_strand(pc->feat, STRAND);
      break;
    }
  }

  /* frame */
  if (!_gtfp_next_token(pc))
    return _gtfp_error(pc, "No frame found");
  frame = strtol(&pc->line[pc->start], NULL, 0);
  if (frame < 0)
    return _gtfp_error(pc, "Invalid frame");
  else
    BAT_Free_Feature_set_frame(pc->feat, frame);

  return 1;
}

int
_gtfp_parse_attributes(BAT_GTF_Parse_Context * pc)
{
  char key[BAT_GTF_LINE_SZ], value[BAT_GTF_LINE_SZ], c;

  if (pc->err)
    return 0;

  pc->gid_attr = NULL;
  pc->tid_attr = NULL;

  while (1) {
    if (!_gtfp_next_token(pc))
      break;
    if (pc->line[pc->start] == '#')
      break;
    c = pc->line[pc->pos];
    pc->line[pc->pos] = '\0';
    strcpy(key, &pc->line[pc->start]);
    pc->line[pc->pos] = c;

    if (!_gtfp_next_token(pc))
      return _gtfp_error(pc, "No value for attribute");
    c = pc->line[pc->pos];
    pc->line[pc->pos] = '\0';
    if (sscanf(&pc->line[pc->start], "\"%1000[^\"]\";", value) == 0)
      return _gtfp_error(pc, "Unintelligible attribute value");

    pc->attr = BAT_Attribute_new();
    BAT_Attribute_set_all(pc->attr, key, value);
    if (!strcmp(key, "gene_id"))
      pc->gid_attr = pc->attr;
    else if (!strcmp(key, "transcript_id"))
      pc->tid_attr = pc->attr;

    if (BAT_Free_Feature_find_attribute(pc->feat, key) != NULL)
      return _gtfp_error(pc, "Multiple attributes with the same key");
    else
      BAT_Free_Feature_set_attribute(pc->feat, pc->attr);

    pc->attr = NULL;

    pc->line[pc->pos] = c;
  }

  if (pc->tid_attr == NULL)
    return _gtfp_error(pc, "No transcript id.");
  if (pc->gid_attr == NULL)
    return _gtfp_error(pc, "No gene id.");

  pc->gid_attr = NULL;
  pc->tid_attr = NULL;

  return 1;
}

int
_gtfp_parse_end_comment(BAT_GTF_Parse_Context * pc)
{
  int i = pc->pos;
  char c = 0;

  for (i = pc->pos; pc->line[i] != '#' && pc->line[i]; i++);
  if (pc->line[i] != '#') {
    if (pc->line[i])
      return _gtfp_error(pc, "Junk at the end of the line.");
  }
  else {
    BAT_Comment_set_offset(pc->cmt, i - pc->pos);
    pc->start = i + 1;
    for (i = pc->start; pc->line[i] && pc->line[i] != '\n'; i++);
    c = 0;
    if (pc->line[i]) {
      c = pc->line[i];
      pc->line[i] = '\0';
    }
    BAT_Comment_set_inline_text(pc->cmt, &pc->line[pc->start]);
  }
  pc->pos = i;

  return 1;
}

int
_gtfp_next_token(BAT_GTF_Parse_Context * pc)
{
  int i = pc->pos;
  while (pc->line[i] && isspace(pc->line[i]) && i < BAT_GTF_LINE_SZ) {
    i++;
  }
  if (i >= BAT_GTF_LINE_SZ)
    return 0;
  pc->start = i;
  while (pc->line[i] && !isspace(pc->line[i]) && i < BAT_GTF_LINE_SZ) {
    i++;
  }
  pc->pos = i;
  if (i >= BAT_GTF_LINE_SZ || pc->pos - pc->start < 1)
    return 0;
  return 1;
}

int
_gtfp_error(BAT_GTF_Parse_Context * pc, char *msg)
{
  fprintf(stderr, "BAT_GeneTab Parser (line %d): %s\n", pc->lineno, msg);
  if (pc->cmt != NULL)
    BAT_Comment_delete(pc->cmt, 1);
  pc->cmt = NULL;
  if (pc->feat != NULL)
    BAT_Free_Feature_delete(pc->feat, 1);
  pc->feat = NULL;
  pc->err = 1;

  return 0;
}
